#include "rghtsiderules.h"
#include <QGroupBox>
#include <QSettings>
#include <QVBoxLayout>
#include <QDebug>
#include "types.h"
#include <QCheckBox>

/*!
  @brief Jedyny konstruktor
  @param parent ustawia rodzica widgetu

  W konstruktorze tworzone są wszystkie widgety znajdujące się w klasie RightSideRules. Ustawiane są treści etykiet, przycisku i QGroupBoxa.
  Ponadto wprowadzany jest suffix dla QSpinBoxów (%) i ustawiany zakres QSpinBoxów <0-100>. Stworzone QSpinBoxy są dodawane do listy
  _spinBoxRulesList a checkBoxy do _checkBoxRulesList.

  Connecty
  Tworzone są dla:
  <ul>
    <li> \b QCheckBoxów </li>
        <ul>
         <li>łączone są ze SpinBoxami tak, aby po zaznaczeniu QCheckBoxów odpowiedni QSpinBox został
            zablokowany lub odblokowany (slot blockSpinBox())
         <li>łączone ze slotem checkNumberChecked() - nie pozwala na zablokowanie wszystkich trzech QSpinBoxów
        </ul>
    <li> \b _saveChangesButton</li>
        <ul>
            <li>ze slotem checkProcent().
        </ul>
    <li> \b QSpinBoxów </li>
    <ul>
        <li>ze slotem colourTheButton() zmieniającym kolor napisu na przycisku
    </ul>
  </ul>

  Na koniec wywoływane są funkcje: configureRightSide() oraz readRightSettingsRules().
*/
RightSideRules::RightSideRules(QWidget *parent) :
    QWidget(parent)
{
    _rightSide = new QGroupBox("Zasady");
    _rightSideLayout = new QVBoxLayout();
    _rightGridLayout = new QGridLayout();

    _officialLabel = new QLabel(tr("oficjalne: "));
    _matchingLanesLabel = new QLabel(tr("dopasowane pasy: "));
    _invertedPriorityLabel = new QLabel(tr("zmiana pierwszeństwa: "));

    _officialSpinBox = new QSpinBox();
    _officialSpinBox->setRange(0, 100);
    _officialSpinBox->setSuffix("%");

    _matchingLanesSpinBox = new QSpinBox();
    _matchingLanesSpinBox->setRange(0, 100);
    _matchingLanesSpinBox->setSuffix("%");

    _invertedPrioritySpinBox = new QSpinBox();
    _invertedPrioritySpinBox->setRange(0, 100);
    _invertedPrioritySpinBox->setSuffix("%");

    _spinBoxRulesList.push_back(_officialSpinBox);
    _spinBoxRulesList.push_back(_matchingLanesSpinBox);
    _spinBoxRulesList.push_back(_invertedPrioritySpinBox);

    _officialCheckBox = new QCheckBox(this);
    connect(_officialCheckBox, SIGNAL(stateChanged(int)), this, SLOT(checkNumberChecked()));
    _matchingCheckBox = new QCheckBox("", this);
    connect(_matchingCheckBox, SIGNAL(stateChanged(int)), this, SLOT(checkNumberChecked()));
    _invertedPriorityCheckBox = new QCheckBox("", this);
    connect(_invertedPriorityCheckBox, SIGNAL(stateChanged(int)), this, SLOT(checkNumberChecked()));

    _checkBoxRulesList.push_back(_officialCheckBox);
    _checkBoxRulesList.push_back(_matchingCheckBox);
    _checkBoxRulesList.push_back(_invertedPriorityCheckBox);

    connect(_officialCheckBox, SIGNAL(toggled(bool)), this, SLOT(blockSpinBox(bool)));
    connect(_matchingCheckBox, SIGNAL(toggled(bool)), this, SLOT(blockSpinBox(bool)));
    connect(_invertedPriorityCheckBox, SIGNAL(toggled(bool)), this, SLOT(blockSpinBox(bool)));

    _saveChangesButton = new QPushButton(this);
    _saveChangesButton->setText(tr("Zapisz zmiany"));

    connect(_saveChangesButton, SIGNAL(clicked()), this, SLOT(checkProcent()));
    connect(_officialSpinBox, SIGNAL(valueChanged(int)), this, SLOT(colourTheButton()));
    connect(_matchingLanesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(colourTheButton()));
    connect(_invertedPrioritySpinBox, SIGNAL(valueChanged(int)), this, SLOT(colourTheButton()));

    configureRightSide();
    readRightSettingsRules();
}

/*!
Funkcja wywoływana wewnątrz konstruktora. Ustawia \b layouty w odpowiedniej kolejności:
głównym layoutem jest _rightSideLayout. Na nim umieszczony jest QGroupBox _rightSide z ustawionym layoutem _rightGridLayout.
Na _rightGridLayout umieszczane są już bezpośrednio widgety.
*/
void RightSideRules::configureRightSide()
{
    this->setLayout(_rightSideLayout);
    _rightSideLayout->addWidget(_rightSide);
    _rightSide->setLayout(_rightGridLayout);

    _rightGridLayout->addWidget(_officialLabel, 0, 0, 1, 2);
    _rightGridLayout->addWidget(_officialSpinBox, 1, 0);
    _rightGridLayout->addWidget(_officialCheckBox, 1, 1);

    _rightGridLayout->addWidget(_matchingLanesLabel, 2, 0, 1, 2);
    _rightGridLayout->addWidget(_matchingLanesSpinBox, 3, 0);
    _rightGridLayout->addWidget(_matchingCheckBox, 3, 1);

    _rightGridLayout->addWidget(_invertedPriorityLabel, 4, 0, 1, 2);
    _rightGridLayout->addWidget(_invertedPrioritySpinBox, 5, 0);
    _rightGridLayout->addWidget(_invertedPriorityCheckBox, 5, 1);


    _rightGridLayout->addWidget(_saveChangesButton, 8, 0, 1, 2, Qt::AlignHCenter );

    _rightSideLayout->addStretch();
}

/*!
Funkcja sprawdza zgodność sumy wszystkich SpinBoxów w widgecie. Suma musi wynosić 100. Jeżeli suma jest różna od tej liczby to wartości SpinBoxów są zmieniane
(z wyjątkiem tych, które są zablokowane). Jeżeli suma jest większa od 100 to obliczana jest nadwyżka i odejmowana równomiernie od odznaczonych QSpinBoxów
(jeżeli nadwyżka nie jest podzielna przez liczbę odznaczonych QSpinBoxów to wtedy reszta z dzielenia jest dodatkowo dodawana do pierwszego odblokowanego QSPinBoxa).
Jeżeli jest to niemożliwe (wartość w QSpinBoxie okazała by się ujemna to odejmujemy wartość także od zablokowanych QSpinBoxów). Podobnie zachowuje się
funkcja w przypadku, gdy suma jest mniejsza od stu. Tylko wtedy zamiast odejmować nadwyżkę dodajemy niedobór. W przypadku odejmowania/dodawania wartości
od zablokowanych QSpinBoxów wykorzystywana jest funkcja uncheckAll().

Na koniec wartości zapisywane za pomocą funkcji saveRightSettingsRules(). Emitowany jest sygnał informujący o zmianie opcji i zmieniany jest kolor czcionki na przycisku na czarny.
  */
void RightSideRules::checkProcent()
{
    int suma = 0;
    int roznica = 0;
    short int reszta = 0;
    short int dodac = 0;
    for (int k = 0; k < _spinBoxRulesList.count(); k++) //sumuje wartosci kolejnych SpinBoxów
        suma += _spinBoxRulesList.at(k)->value();
    if (suma != 100)
    {
        int ileOdznaczonych = 0,  sumaOdznaczonych = 0;
        //2. zliczam liczbę odznaczonych
        foreach ( QSpinBox* spinBox,  _spinBoxRulesList)
            if (spinBox->isEnabled())
            {
                ileOdznaczonych++;
                sumaOdznaczonych += spinBox->value();
            }

        QList <bool> checkedOnes;
        if (suma > 100)
        {
            int nadwyzka =  suma - 100;
            if ((nadwyzka > sumaOdznaczonych) || (ileOdznaczonych == 0)) // chcemy odjąć nadwyżkę od odznaczonych, ale suma odznaczonych jest zbyt mała
            {
                checkedOnes = uncheckAll();
                ileOdznaczonych = _spinBoxRulesList.count();
                sumaOdznaczonych = suma;
            }
        }
        if (suma < 100)
        {
            int niedomiar =  100 - suma;
            if ((sumaOdznaczonych + niedomiar > 100) || (ileOdznaczonych == 0)) // nie zmieścimy niedomiaru w Odznaczonych, trzeba też dodać do zaznaczonych
            {
                checkedOnes = uncheckAll();
                ileOdznaczonych = _spinBoxRulesList.count();
                sumaOdznaczonych = suma;
            }
        }

        // teraz trzeba wykonać zwykły podzial między odznaczone.
        // jeżeli na końcu checkedOnes.size() != 0, to odpalamy jeszcze checkFromList(i, checkedOnes);
        roznica = suma - 100;
        dodac = -1 * (roznica / ileOdznaczonych); // -1, żeby odejmować gdy suma > 100 i dodawać gdy suma < 100.
        reszta = -1 * (roznica % ileOdznaczonych );

        for (int j = 0; j < _spinBoxRulesList.count(); j++)
        {
            if (_spinBoxRulesList.at(j)->isEnabled())
            {
                if ((_spinBoxRulesList[j]->value() == 0) && (dodac + reszta < 0))
                    continue;
                _spinBoxRulesList.at(j)->setValue(_spinBoxRulesList.at(j)->value() + dodac + reszta); //dodaje do kazdego SpinBoxa wartosc, zeby suma byla 100;
                reszta = 0; // resztę dodajemy tylko do pierwszego z brzegu.
            }
        }
        if (checkedOnes.size() != 0) //przywracane są "ticki" QCheckBoxów sprzed wywołania funkcji checkAll()
        {
            for (int i = 0; i < checkedOnes.size(); i++)
            {
                switch (i)
                {
                    case 0:
                        _officialCheckBox->setEnabled(!checkedOnes.at(i));
                        break;
                    case 1:
                        _matchingCheckBox->setEnabled(!checkedOnes.at(i));
                        break;
                    case 2:
                        _invertedPriorityCheckBox->setEnabled(!checkedOnes.at(i));
                        break;
                    default:
                        break;
                }
            }
        }
        for (int k = 0; k < _spinBoxRulesList.count(); k++) //sumuje wartosci kolejnych SpinBoxów. W przypadku gdy jest większa niż 100 ustawiam różnicę, a kolejne ewentualnie zera
            suma += _spinBoxRulesList.at(k)->value();
        if (suma != 100)
            checkProcent();
    }
    //zapisuje opcje do QSettings

    saveRightSettingsRules();
    emit(newRightSettings());
    changeButtonTextColor(_saveChangesButton, Qt::black);
 }


/*!
  Funkcja pomocniczna dla slotu checkProcent().
  Zapisuje stany QCheckBoxów do listy checkedOnes. Następnie
    ustawia QCheckBoxy na odznaczone i zwraca listę uprzednio zapisanych stanów.
  */
QList <bool> RightSideRules::uncheckAll()
{
    QList <bool> checkedOnes;

    checkedOnes.append(_officialCheckBox->isChecked());
    checkedOnes.append(_matchingCheckBox->isChecked());
    checkedOnes.append(_invertedPriorityCheckBox->isChecked());

    _officialCheckBox->setChecked(false);
    _matchingCheckBox->setChecked(false);
    _invertedPriorityCheckBox->setChecked(false);
    return checkedOnes;
}

/*!
  Funkcja sprawdza, czy zaznaczany przez użytkownika QCheckBox nie jest ostatnim odznaczonym.
  Jeżeli jest to z powrotem ustawia na niezaznaczony. Dzięki temu nie ma możliwości, aby wszystkie QCheckBoxy zostały zaznaczone.
  */
void RightSideRules::checkNumberChecked()
{
    int ile = 0;
    for (int k = 0; k < _checkBoxRulesList.count(); k++)
        if (_checkBoxRulesList.at(k)->isChecked() )
            ile++;
    if (ile == _spinBoxRulesList.count())
    {
        ((QCheckBox*)sender())->setCheckState(Qt::Unchecked);
    }
}

/*!
  Zapisuje opcje na dysk za pomocą QSettings w grupie rightRules. Zobacz więcej na \link stronaZapisOpcji \endlink.
  Funkcja jest wywoływana w funkcji checkProcent() (po sprawdzeniu, czy suma wartości w pinBoxach jest równa 100)
  */
void RightSideRules::saveRightSettingsRules()
{
    QSettings opcje("TabMar", "RoundaboutSimulator");//C:\settings
    opcje.beginGroup("rightRules");
            opcje.setValue("oficial", _officialSpinBox->value() );
            opcje.setValue("matchingLanes", _matchingLanesSpinBox->value() );
            opcje.setValue("invertedPriority", _invertedPrioritySpinBox->value() );
   opcje.endGroup();
}

/*!
  Analogicznie do saveRightSettingsRules funkcja odczytuje opcje z dysku za pomocą QSettings w grupie rightRules. Zobacz więcej na \link stronaZapisOpcji \endlink.
  Jeżeli na dysku nie ma zapisanych opcji wczytywane są wartości domyślne (100% dla zasad oficjalnych).
  Funkcja wywoływana jest na końcu konstruktora.
  */
void RightSideRules::readRightSettingsRules(QString name)
{
    QSettings opcje("TabMar", name);
        opcje.beginGroup("rightRules");
            _officialSpinBox->setValue(opcje.value("oficial", 100).toInt());
            _matchingLanesSpinBox->setValue(opcje.value("matchingLanes", 0).toInt());
            _invertedPrioritySpinBox->setValue(opcje.value("invertedPriority", 0).toInt());
        opcje.endGroup();
}

/*!
    Slot podłączony do wszystkich trzech QCheckBoxów, który blokuje/odblokowuje QSpinBoxa odpowiadającego
    zaznaczonemu/odznaczonemu QCheckBoxowi.
  */
void RightSideRules::blockSpinBox(bool b)
{
    if (sender() == _officialCheckBox)
        _officialSpinBox->setEnabled(!_officialCheckBox->isChecked());
    if (sender() == _matchingCheckBox)
        _matchingLanesSpinBox->setEnabled(!_matchingCheckBox->isChecked());
    if (sender() == _invertedPriorityCheckBox)
        _invertedPrioritySpinBox->setEnabled(!_invertedPriorityCheckBox->isChecked());
}

/*!
    \return _saveChangesButton wskaźnik do przycisku zapisującego zmmiany
  */
QPushButton *RightSideRules::getCmdSaveChanges()
{    return _saveChangesButton;   }

/*!
    Zmienia kolor czcionki na przycisku _saveChangesButton na czerwony wywołując funkcję changeButtonTextColor()
    z pliku types.h
  */
void RightSideRules::colourTheButton()
{   changeButtonTextColor(_saveChangesButton, Qt::red);   }
